home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / hity wydania / Ubuntu 9.10 PL / karmelkowy-koliberek-9.10-netbook-remix-PL.iso / casper / filesystem.squashfs / usr / share / pyshared / aptdaemon / defer.py < prev    next >
Text File  |  2009-10-14  |  16KB  |  421 lines

  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. """Simplified Twisted Deferreds."""
  4. # Copyright (C) 2008-2009 Sebastian Heinlein <devel@glatzor.de>
  5. #
  6. # This program is free software; you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation; either version 2 of the License, or
  9. # any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License along
  17. # with this program; if not, write to the Free Software Foundation, Inc.,
  18. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  19.  
  20. __author__  = "Sebastian Heinlein <devel@glatzor.de>"
  21.  
  22. from functools import wraps
  23. import sys
  24.  
  25. import dbus
  26.  
  27. class AlreadyCalledDeferred(Exception):
  28.     """The Deferred is already running a callback."""
  29.  
  30.  
  31. class DeferredException(object):
  32.     """Allows to defer exceptions."""
  33.  
  34.     def __init__(self, type=None, value=None, traceback=None):
  35.         """Return a new DeferredException instance.
  36.  
  37.         If type, value and traceback are not specified the infotmation
  38.         will be retreieved from the last caught exception:
  39.  
  40.         >>> try:
  41.         ...     raise Exception("Test")
  42.         ... except:
  43.         ...     deferred_exc = DeferredException()
  44.         >>> deferred_exc.raise_exception()
  45.         Traceback (most recent call last):
  46.             ...
  47.         Exception: Test
  48.  
  49.         Alternatively you can set the exception manually:
  50.  
  51.         >>> exception = Exception("Test 2")
  52.         >>> deferred_exc = DeferredException(exception)
  53.         >>> deferred_exc.raise_exception()
  54.         Traceback (most recent call last):
  55.             ...
  56.         Exception: Test 2
  57.         """
  58.         self.type = type
  59.         self.value = value
  60.         self.traceback = traceback
  61.         if isinstance(type, Exception):
  62.             self.type = type.__class__
  63.             self.value = type
  64.         elif not type or not value:
  65.             self.type, self.value, self.traceback = sys.exc_info()
  66.  
  67.     def raise_exception(self):
  68.         """Raise the stored exception."""
  69.         raise self.type, self.value, self.traceback
  70.  
  71.     def catch(self, *errors):
  72.         """Check if the stored exception is a subclass of one of the
  73.         provided exception classes. If this is the case return the
  74.         matching exception class. Otherwise raise the stored exception.
  75.  
  76.         >>> exc = DeferredException(SystemError())
  77.         >>> exc.catch(Exception) # Will catch the exception and return it
  78.         <type 'exceptions.Exception'>
  79.         >>> exc.catch(OSError)   # Won't catch and raise the stored exception
  80.         Traceback (most recent call last):
  81.             ...
  82.         SystemError
  83.  
  84.         This method can be used in errbacks of a Deferred:
  85.  
  86.         >>> def dummy_errback(deferred_exception):
  87.         ...     '''Error handler for OSError'''
  88.         ...     deferred_exception.catch(OSError)
  89.         ...     return "catched"
  90.  
  91.         The above errback can handle an OSError:
  92.  
  93.         >>> deferred = Deferred()
  94.         >>> deferred.add_errback(dummy_errback)
  95.         >>> deferred.errback(OSError())
  96.         >>> deferred.result
  97.         'catched'
  98.  
  99.         But fails to handle a SystemError:
  100.  
  101.         >>> deferred2 = Deferred()
  102.         >>> deferred2.add_errback(dummy_errback)
  103.         >>> deferred2.errback(SystemError())
  104.         >>> deferred2.result                             #doctest: +ELLIPSIS
  105.         <aptdaemon.defer.DeferredException object at 0x...>
  106.         >>> deferred2.result.value
  107.         SystemError()
  108.         """
  109.         for err in errors:
  110.             if issubclass(self.type, err):
  111.                 return err
  112.         self.raise_exception()
  113.  
  114.  
  115. class Deferred(object):
  116.     """The Deferred allows to chain callbacks.
  117.  
  118.     There are two type of callbacks: normal callbacks and errbacks, which
  119.     handle an exception in a normal callback.
  120.  
  121.     The callbacks are processed in pairs consisting of a normal callback
  122.     and an errback. A normal callback will return its result to the
  123.     callback of the next pair.  If an exception occurs, it will be handled
  124.     by the errback of the next pair. If an errback doesn't raise an error
  125.     again, the callback of the next pair will be called with the return
  126.     value of the errback. Otherwise the exception of the errback will be
  127.     returned to the errback of the next pair.
  128.  
  129.       CALLBACK1      ERRBACK1
  130.        |     \       /     |
  131.    result failure  result failure
  132.        |       \   /       |
  133.        |        \ /        |
  134.        |         X         |
  135.        |        / \        |
  136.        |       /   \       |
  137.        |      /     \      |
  138.       CALLBACK2      ERRBACK2
  139.        |     \       /     |
  140.    result failure  result failure
  141.        |       \   /       |
  142.        |        \ /        |
  143.        |         X         |
  144.        |        / \        |
  145.        |       /   \       |
  146.        |      /     \      |
  147.       CALLBACK3      ERRBACK3
  148.       """
  149.  
  150.     def __init__(self):
  151.         """Return a new Deferred instance."""
  152.         self.callbacks = []
  153.         self.errbacks = []
  154.         self.called = False
  155.         self.paused = False
  156.         self._running = False
  157.  
  158.     def add_callbacks(self, callback, errback=None,
  159.                       callback_args=None, callback_kwargs=None,
  160.                       errback_args=None, errback_kwargs=None):
  161.         """Add a pair of callables (function or method) to the callback and
  162.         errback chain.
  163.  
  164.         Keyword arguments:
  165.         callback -- the next chained challback
  166.         errback -- the next chained errback
  167.         callback_args -- list of additional arguments for the callback
  168.         callback_kwargs -- dict of additional arguments for the callback
  169.         errback_args -- list of additional arguments for the errback
  170.         errback_kwargs -- dict of additional arguments for the errback
  171.  
  172.         In the following example the first callback pairs raises an
  173.         exception that is catched by the errback of the second one and
  174.         processed by the third one.
  175.  
  176.         >>> def callback(previous):
  177.         ...     '''Return the previous result.'''
  178.         ...     return "Got: %s" % previous
  179.         >>> def callback_raise(previous):
  180.         ...     '''Fail and raise an exception.'''
  181.         ...     raise Exception("Test")
  182.         >>> def errback(error):
  183.         ...     '''Recover from an exception.'''
  184.         ...     #error.catch(Exception)
  185.         ...     return "catched"
  186.         >>> deferred = Deferred()
  187.         >>> deferred.callback("start")
  188.         >>> deferred.result
  189.         'start'
  190.         >>> deferred.add_callbacks(callback_raise, errback)
  191.         >>> deferred.result                             #doctest: +ELLIPSIS
  192.         <aptdaemon.defer.DeferredException object at 0x...>
  193.         >>> deferred.add_callbacks(callback, errback)
  194.         >>> deferred.result
  195.         'catched'
  196.         >>> deferred.add_callbacks(callback, errback)
  197.         >>> deferred.result
  198.         'Got: catched'
  199.         """
  200.         assert callable(callback)
  201.         assert errback is None or callable(errback)
  202.         if errback is None:
  203.             errback = _passthrough
  204.         self.callbacks.append(((callback,
  205.                                 callback_args or ([]),
  206.                                 callback_kwargs or ({})),
  207.                                (errback or (_passthrough),
  208.                                 errback_args or ([]),
  209.                                 errback_kwargs or ({}))))
  210.         if self.called:
  211.             self._next()
  212.  
  213.     def add_errback(self, func, *args, **kwargs):
  214.         """Add a callable (function or method) to the errback chain only.
  215.  
  216.         If there isn't any exception the result will be passed through to
  217.         the callback of the next pair.
  218.  
  219.         The first argument is the callable instance followed by any
  220.         additional argument that will be passed to the errback.
  221.  
  222.         The errback method will get the most recent DeferredException and
  223.         and any additional arguments that was specified in add_errback.
  224.  
  225.         If the errback can catch the exception it can return a value that
  226.         will be passed to the next callback in the chain. Otherwise the
  227.         errback chain will not be processed anymore.
  228.  
  229.         See the documentation of defer.DeferredException.catch for
  230.         further information.
  231.  
  232.         >>> def catch_error(deferred_error, ignore=False):
  233.         ...     if ignore:
  234.         ...         return "ignored"
  235.         ...     deferred_error.catch(Exception)
  236.         ...     return "catched"
  237.         >>> deferred = Deferred()
  238.         >>> deferred.errback(SystemError())
  239.         >>> deferred.add_errback(catch_error, ignore=True)
  240.         >>> deferred.result
  241.         'ignored'
  242.         """
  243.         self.add_callbacks(_passthrough, func, errback_args=args,
  244.                            errback_kwargs=kwargs)
  245.  
  246.     def add_callback(self, func, *args, **kwargs):
  247.         """Add a callable (function or method) to the callback chain only.
  248.  
  249.         An error would be passed through to the next errback.
  250.  
  251.         The first argument is the callable instance followed by any
  252.         additional argument that will be passed to the callback.
  253.  
  254.         The callback method will get the result of the previous callback
  255.         and any additional arguments that was specified in add_callback.
  256.  
  257.         >>> def callback(previous, counter=False):
  258.         ...     if counter:
  259.         ...         return previous + 1
  260.         ...     return previous
  261.         >>> deferred = Deferred()
  262.         >>> deferred.add_callback(callback, counter=True)
  263.         >>> deferred.callback(1)
  264.         >>> deferred.result
  265.         2
  266.         """
  267.         self.add_callbacks(func, _passthrough, callback_args=args,
  268.                            callback_kwargs=kwargs)
  269.  
  270.     def errback(self, error=None):
  271.         """Start processing the errorback chain starting with the
  272.         provided exception or DeferredException.
  273.  
  274.         If an exception is specified it will be wrapped into a
  275.         DeferredException. It will be send to the first errback or stored 
  276.         as finally result if not any further errback has been specified yet.
  277.  
  278.         >>> deferred = Deferred()
  279.         >>> deferred.errback(Exception("Test Error"))
  280.         >>> deferred.result                             #doctest: +ELLIPSIS
  281.         <aptdaemon.defer.DeferredException object at 0x...>
  282.         >>> deferred.result.raise_exception()
  283.         Traceback (most recent call last):
  284.             ...
  285.         Exception: Test Error
  286.         """
  287.         if self.called:
  288.             raise AlreadyCalledDeferred()
  289.         if not isinstance(error, DeferredException):
  290.             assert isinstance(error, Exception)
  291.             error = DeferredException(error.__class__, error, None)
  292.         self.called = True
  293.         self.result = error
  294.         self._next()
  295.  
  296.     def callback(self, result=None):
  297.         """Start processing the callback chain starting with the
  298.         provided result.
  299.  
  300.         It will be send to the first callback or stored as finally
  301.         one if not any further callback has been specified yet.
  302.  
  303.         >>> deferred = Deferred()
  304.         >>> deferred.callback("done")
  305.         >>> deferred.result
  306.         'done'
  307.         """
  308.         if self.called:
  309.             raise AlreadyCalledDeferred()
  310.         self.called = True
  311.         self.result = result
  312.         self._next()
  313.  
  314.     def _continue(self, result):
  315.         """Continue processing the Deferred with the given result."""
  316.         self.result = result
  317.         self.paused = False
  318.         if self.called:
  319.             self._next()
  320.  
  321.     def _next(self):
  322.         """Process the next callback."""
  323.         if self._running or self.paused:
  324.             return
  325.         while self.callbacks:
  326.             # Get the next callback pair
  327.             next_pair = self.callbacks.pop(0)
  328.             # Continue with the errback if the last result was an exception
  329.             callback, args, kwargs = next_pair[isinstance(self.result,
  330.                                                           DeferredException)]
  331.             try:
  332.                 self.result = callback(self.result, *args, **kwargs)
  333.             except:
  334.                 self.result = DeferredException()
  335.             finally:
  336.                 self._running = False
  337.             if isinstance(self.result, Deferred):
  338.                 # If a Deferred was returned add this deferred as callbacks to
  339.                 # the returned one. As a result the processing of this Deferred
  340.                 # will be paused until all callbacks of the returned Deferred
  341.                 # have been performed
  342.                 self.result.add_callbacks(self._continue, self._continue)
  343.                 self.paused == True
  344.                 break
  345.  
  346.         if isinstance(self.result, DeferredException):
  347.             # Print the exception to stderr and stop if there aren't any
  348.             # further errbacks to process
  349.             sys.excepthook(self.result.type, self.result.value,
  350.                            self.result.traceback)
  351.             return False
  352.  
  353. def defer(func, *args, **kwargs):
  354.     """Invoke the given function that may or not may be a Deferred.
  355.  
  356.     If the return object of the function call is a Deferred return, it.
  357.     Otherwise wrap it into a Deferred.
  358.  
  359.     >>> defer(lambda x: x, 10)                 #doctest: +ELLIPSIS
  360.     <aptdaemon.defer.Deferred object at 0x...>
  361.  
  362.     >>> deferred = defer(lambda x: x, "done")
  363.     >>> deferred.result
  364.     'done'
  365.  
  366.     >>> deferred = Deferred()
  367.     >>> defer(lambda: deferred) == deferred
  368.     True
  369.     """
  370.     assert callable(func)
  371.     try:
  372.         result = func(*args, **kwargs)
  373.     except:
  374.         result = DeferredException()
  375.     if isinstance(result, Deferred):
  376.         return result
  377.     deferred = Deferred()
  378.     deferred.callback(result)
  379.     return deferred
  380.  
  381.  
  382. def dbus_deferred_method(*args, **kwargs):
  383.     """Export the decorated method on the D-Bus and handle a maybe
  384.     returned Deferred.
  385.  
  386.     This decorator can be applied to methods in the same way as the
  387.     @dbus.service.method method, but it correctly handles the case where
  388.     the method returns a Deferred.
  389.  
  390.     This decorator was kindly taken from James Henstridge blog post and
  391.     adopted:
  392.     http://blogs.gnome.org/jamesh/2009/07/06/watching-iview-with-rygel/
  393.     """
  394.     def decorator(function):
  395.         function = dbus.service.method(*args, **kwargs)(function)
  396.         @wraps(function)
  397.         def wrapper(*args, **kwargs):
  398.             def ignore_none_callback(*cb_args):
  399.                 # The deferred method at least returns an tuple containing
  400.                 # only None. Ignore this case.
  401.                 if cb_args == (None,):
  402.                     dbus_callback()
  403.                 else:
  404.                     dbus_callback(*cb_args)
  405.             dbus_callback = kwargs.pop('_dbus_callback')
  406.             dbus_errback = kwargs.pop('_dbus_errback')
  407.             deferred = defer(function, *args, **kwargs)
  408.             deferred.add_callback(ignore_none_callback)
  409.             deferred.add_errback(lambda error: dbus_errback(error.value))
  410.         # The @wraps decorator has copied over the attributes added by
  411.         # the @dbus.service.method decorator, but we need to manually
  412.         # set the async callback attributes.
  413.         wrapper._dbus_async_callbacks = ('_dbus_callback', '_dbus_errback')
  414.         return wrapper
  415.     return decorator
  416.  
  417. def _passthrough(arg):
  418.     return arg
  419.  
  420. # vim:tw=4:sw=4:et
  421.